// ----------------------------------------------------------------------------
// diabindSession.h
// DIA Session
// ----------------------------------------------------------------------------

#pragma once

// ----------------------------------------------------------------------------

#include "diabindEnumLineNumbers.h"
#include "diabindEnumDebugStreams.h"

// ----------------------------------------------------------------------------

namespace diabind
{

// ----------------------------------------------------------------------------

interface ISession
{
    virtual ~ISession() {}

    virtual ULONGLONG get_loadAddress() = 0;

    virtual void put_loadAddress( ULONGLONG NewVal ) = 0;

    virtual CSymbolPtr get_globalScope() = 0;

    virtual CSymbolPtr findSymbolByAddr(
        DWORD isect,
        DWORD offset,
        DWORD symtag
    ) = 0;

    virtual python::tuple findSymbolByRVAEx(
        DWORD rva,
        DWORD symtag
    ) = 0;

    virtual python::tuple findSymbolByVAEx(
        ULONGLONG va,
        DWORD symtag
    ) = 0;

    virtual CSymbolPtr symbolById(DWORD id) = 0;

    virtual CSourceFilePtr findFileById(DWORD uniqueId) = 0;

    virtual CEnumLineNumbersPtr findLinesByAddr( 
        DWORD seg,
        DWORD offset,
        DWORD length
    ) = 0;

    virtual CEnumLineNumbersPtr findLinesByRVA( 
        DWORD rva,
        DWORD length
    ) = 0;

    virtual CEnumLineNumbersPtr findLinesByVA( 
        ULONGLONG va,
        DWORD length
    ) = 0;

    virtual CEnumDebugStreamsPtr getEnumDebugStreams() = 0;
};
typedef boost::shared_ptr< ISession > CSessionPtr;

// ----------------------------------------------------------------------------

template< class DiaSessionT >
class CSessionImpl : public ISession
{
    typedef _com_IIID< DiaSessionT, &__uuidof(DiaSessionT) > CIiid;
    typedef _com_ptr_t< CIiid > CImplPtr;

public:
    explicit CSessionImpl(const CImplPtr &pSession)
        : m_pSession(pSession)
    {
    }

    virtual ULONGLONG get_loadAddress() override
    {
        ULONGLONG RetVal;
        HRESULT hResult = m_pSession->get_loadAddress(&RetVal);
        if (S_OK != hResult)
            throw CException(hResult, "IDiaSession::get_loadAddress");
        return RetVal;
    }

    virtual void put_loadAddress( 
        /* [in] */ ULONGLONG NewVal
    ) override
    {
        HRESULT hResult = m_pSession->put_loadAddress(NewVal);
        if (S_OK != hResult)
            throw CException(hResult, "IDiaSession::put_loadAddress");
    }

    virtual CSymbolPtr get_globalScope() override
    {
        return 
            GetSymbol(
                &DiaSessionT::get_globalScope,
                "IDiaSession::get_globalScope");
    }

    virtual CSymbolPtr findSymbolByAddr(
        DWORD isect,
        DWORD offset,
        DWORD symtag
    ) override
    {
        return 
            FindSymbol(
                &DiaSessionT::findSymbolByAddr,
                "IDiaSession::findSymbolByAddr",
                isect,
                offset,
                symtag);
    }

    virtual python::tuple findSymbolByRVAEx(
        DWORD rva,
        DWORD symtag
    ) override
    {
        long displacement;
        CSymbolPtr pSymbol = 
            FindSymbolEx(
                &DiaSessionT::findSymbolByRVAEx,
                "IDiaSession::findSymbolByRVAEx",
                rva,
                symtag,
                &displacement);
        return python::make_tuple(pSymbol, displacement);
    }
    
    virtual python::tuple findSymbolByVAEx(
        ULONGLONG va,
        DWORD symtag
    ) override
    {
        long displacement;
        CSymbolPtr pSymbol = 
            FindSymbolEx(
                &DiaSessionT::findSymbolByVAEx,
                "IDiaSession::findSymbolByVAEx",
                va,
                symtag,
                &displacement);
        return python::make_tuple(pSymbol, displacement);
    }

    virtual CSymbolPtr symbolById(DWORD id) override
    {
        return 
            GetSymbolById(
                &DiaSessionT::symbolById,
                "IDiaSession::symbolById",
                id);
    }

    virtual CSourceFilePtr findFileById(DWORD uniqueId) override
    {
        return
            FindFileById(
                &DiaSessionT::findFileById,
                "IDiaSession::findFileById",
                uniqueId);
    }

    virtual CEnumLineNumbersPtr findLinesByAddr( 
        DWORD seg,
        DWORD offset,
        DWORD length
    ) override
    {
        return 
            FindLines3Arg(
                &DiaSessionT::findLinesByAddr,
                "IDiaSession::findLinesByAddr",
                seg,
                offset,
                length);
    }

    virtual CEnumLineNumbersPtr findLinesByRVA( 
        DWORD rva,
        DWORD length
    ) override
    {
        return 
            FindLines2Arg(
                &DiaSessionT::findLinesByRVA,
                "IDiaSession::findLinesByRVA",
                rva,
                length);
    }

    virtual CEnumLineNumbersPtr findLinesByVA( 
        ULONGLONG va,
        DWORD length
    ) override
    {
        return 
            FindLines2Arg(
                &DiaSessionT::findLinesByVA,
                "IDiaSession::findLinesByVA",
                va,
                length);
    }

    virtual CEnumDebugStreamsPtr getEnumDebugStreams() override
    {
        return
            GetEnumDebugStreamsT(
                &DiaSessionT::getEnumDebugStreams,
                "IDiaSession::getEnumDebugStreams");
    }

protected:
    template< class DiaSymbolT >
    CSymbolPtr GetSymbol(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(DiaSymbolT **),
        const char *szMethodName
    )
    {
        _com_ptr_t< _com_IIID<DiaSymbolT, &__uuidof(DiaSymbolT)> > pSymbol;
        HRESULT hResult = (m_pSession->*pMethod)(&pSymbol);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CSymbolPtr( new CSymbolImpl<DiaSymbolT>(pSymbol) );
    }

    template< class DiaSymbolT, typename Arg1T, typename Arg2T, typename RetArgT >
    CSymbolPtr FindSymbolEx(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(Arg1T, Arg2T, DiaSymbolT **, RetArgT *),
        const char *szMethodName,
        Arg1T Arg1,
        DWORD Arg2,
        RetArgT *RetArg
    )
    {
        _com_ptr_t< _com_IIID<DiaSymbolT, &__uuidof(DiaSymbolT)> > pSymbol;
        HRESULT hResult = (m_pSession->*pMethod)(Arg1, static_cast<Arg2T>(Arg2), &pSymbol, RetArg);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CSymbolPtr( new CSymbolImpl<DiaSymbolT>(pSymbol) );
    }

    template< class DiaSymbolT, typename Arg1T, typename Arg2T, typename Arg3T >
    CSymbolPtr FindSymbol(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(Arg1T, Arg2T, Arg3T, DiaSymbolT **),
        const char *szMethodName,
        Arg1T Arg1,
        Arg2T Arg2,
        DWORD Arg3
    )
    {
        _com_ptr_t< _com_IIID<DiaSymbolT, &__uuidof(DiaSymbolT)> > pSymbol;
        HRESULT hResult = (m_pSession->*pMethod)(Arg1, Arg2, static_cast<Arg3T>(Arg3), &pSymbol);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CSymbolPtr( new CSymbolImpl<DiaSymbolT>(pSymbol) );
    }

    template< class DiaSymbolT >
    CSymbolPtr GetSymbolById(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(DWORD, DiaSymbolT **),
        const char *szMethodName,
        DWORD id
    )
    {
        _com_ptr_t< _com_IIID<DiaSymbolT, &__uuidof(DiaSymbolT)> > pSymbol;
        HRESULT hResult = (m_pSession->*pMethod)(id, &pSymbol);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CSymbolPtr( new CSymbolImpl<DiaSymbolT>(pSymbol) );
    }

    template< class DiaSourceFileT >
    CSourceFilePtr FindFileById(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(DWORD, DiaSourceFileT **),
        const char *szMethodName,
        DWORD uniqueId
    )
    {
        _com_ptr_t< _com_IIID<DiaSourceFileT, &__uuidof(DiaSourceFileT)> > pSourceFile;
        HRESULT hResult = (m_pSession->*pMethod)(uniqueId, &pSourceFile);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CSourceFilePtr( new CSourceFileImpl<DiaSourceFileT>(pSourceFile) );
    }

    template< class DiaEnumLineNumbersT, typename Arg1T, typename Arg2T, typename Arg3T >
    CEnumLineNumbersPtr FindLines3Arg(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(Arg1T, Arg2T, Arg3T, DiaEnumLineNumbersT **),
        const char *szMethodName,
        Arg1T Arg1,
        Arg2T Arg2,
        Arg3T Arg3
    )
    {
        _com_ptr_t< _com_IIID<DiaEnumLineNumbersT, &__uuidof(DiaEnumLineNumbersT)> > pEnumLineNumbers;
        HRESULT hResult = (m_pSession->*pMethod)(Arg1, Arg2, Arg3, &pEnumLineNumbers);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CEnumLineNumbersPtr( new CEnumLineNumbersImpl<DiaEnumLineNumbersT>(pEnumLineNumbers) );
    }

    template< class DiaEnumLineNumbersT, typename Arg1T, typename Arg2T >
    CEnumLineNumbersPtr FindLines2Arg(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(Arg1T, Arg2T, DiaEnumLineNumbersT **),
        const char *szMethodName,
        Arg1T Arg1,
        Arg2T Arg2
    )
    {
        _com_ptr_t< _com_IIID<DiaEnumLineNumbersT, &__uuidof(DiaEnumLineNumbersT)> > pEnumLineNumbers;
        HRESULT hResult = (m_pSession->*pMethod)(Arg1, Arg2, &pEnumLineNumbers);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CEnumLineNumbersPtr( new CEnumLineNumbersImpl<DiaEnumLineNumbersT>(pEnumLineNumbers) );
    }

    template< class DiaEnumDebugStreamsT >
    CEnumDebugStreamsPtr GetEnumDebugStreamsT(
        HRESULT (STDMETHODCALLTYPE DiaSessionT::*pMethod)(DiaEnumDebugStreamsT **),
        const char *szMethodName
    )
    {
        _com_ptr_t< _com_IIID<DiaEnumDebugStreamsT, &__uuidof(DiaEnumDebugStreamsT)> > pEnumDebugStreams;
        HRESULT hResult = (m_pSession->*pMethod)(&pEnumDebugStreams);
        if (S_OK != hResult)
            throw CException(hResult, szMethodName);
        return CEnumDebugStreamsPtr( new CEnumDebugStreamsImpl<DiaEnumDebugStreamsT>(pEnumDebugStreams) );
    }

private:
    CImplPtr m_pSession;
};

// ----------------------------------------------------------------------------

}   // namespace diabind

// ----------------------------------------------------------------------------
